/* ***** BEGIN LICENSE BLOCK *****
 *   Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is JsWs.
 *
 * The Initial Developer of the Original Code is
 * Chris Winberry.
 * Portions created by the Initial Developer are Copyright (C) 2007
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 * 
 * ***** END LICENSE BLOCK ***** */


/**
 * @class Exception used to signal the end of processing for a request
 * @constructor
 * @base ExBase
 * @param {String} msg Description of exception
 * @param {Number} code (optional) Error code
 * @param {Exception} ex (optional) An earlier exception to wrap
 * @see Response#end
 */
function ExResponseEnd (msg, code, ex) { ExResponseEnd.baseConstructor.call(this, msg, code, ex); }
SubClass(ExBase, ExResponseEnd);


/**
 * @class Class to represent an HTTP response
 * @constructor
 */
function Response (handlerObj) {
	/** @private */
	this.handlerObj = handlerObj;
	/**
	 * Flag to indicate whether output should be buffered or sent immediately
	 * @type Boolean
	 */
	this.buffer = true;
	/** @private */
	this.headersSent = false;
	/** @private */
	this.output = new Array();
	/**
	 * HTTP status code
	 * @type Number
	 */
	this.status = 200;
	/**
	 * Headers collection
	 * @type Hash
	 */
	this.headers = new Array();
	/**
	 * Cookies collection
	 * @type Cookie[]
	 */
	this.cookies = new Array();

	this.contentType("text/html");
}


/**
 * Write data to output (client)
 * @param {String} data Data to write
 */
Response.prototype.write = function (data) {
	if (this.buffer) {
		this.output[this.output.length] = data;
	} else {
		this.handlerObj.write(this.renderHeaders());
		this.handlerObj.write(data);
	}
}


/**
 * Writes any buffered data to output (client)
 */
Response.prototype.flush = function () {
	this.handlerObj.write(this.renderHeaders());
	if (this.output.length)
		this.handlerObj.write(this.output.join('')); this.output = new Array();
	//this.buffer = false; // ???
}


/**
 * Removes any buffered output
 */
Response.prototype.clear = function () {
	this.output = new Array();
}


/**
 * Immediately halts execution of the current page. Any buffered content
 * 	is flushed.
 * @exception ExResponseEnd
 */
Response.prototype.end = function () {
	if (this.buffer) {
		this.headers["Content-Length"] = this.output.join('').length;
	}
	this.flush();
	throw(new ExResponseEnd());
}


/**
 * Sets the content-type header of the response
 * @param {String} type MIME type
 */
Response.prototype.contentType = function (type) {
	this.headers["Content-Type"] = type;
}


/**
 * Causes the response to issue a redirect to the client.
 * @param {String} location URL to redirect to. Passing a null
 *  clears the redirect
 */
Response.prototype.redirect = function (location) {
	if (location != null) {
		this.headers["Location"] = location;
		this.status = 302;
	} else {
		delete this.headers["Location"]; // ???
		this.status = 200; // ???
	}
}


/**
 * Adds a cookie to the response
 * @param {Cookie} cookie Cookie to add
 */
Response.prototype.setCookie = function (cookie) {
	this.cookies[cookie.name] = cookie;
}


/** @private */
Response.prototype.statText = {
	200: "OK",
	301: "Moved Permanently",
	302: "Found",
	304: "Not Modified",
	400: "Bad Request",
	401: "Unauthorized",
	403: "Forbidden",
	404: "Not Found",
	405: "Method Not Allowed",
	500: "Internal Server Error",
	501: "Not Implemented",
	503: "Service Unavailable",
	505: "HTTP Version Not Supported"
};

/** @private */
Response.prototype.renderStatus = function () {
	return("HTTP/1.1 " + this.status + " " + ((this.statText[this.status] == null) ? "UNKNOWN" : this.statText[this.status]) + "\r\n");
}


/** @private */
Response.prototype.renderHeaders = function () {
	if (this.headersSent) { return(""); }
	this.headersSent = true;
	var sRetval = this.renderStatus();
	for (var key in this.headers)
		sRetval += key + ": " + this.headers[key] + "\r\n";
	for (var key in this.cookies)
		sRetval += this.cookies[key] + "\r\n";
	return(sRetval + "\r\n");
}


/**
 * Runs cleanup on internal objects
 */
Response.prototype.destroy = function () {
	this.handlerObj.destroy();
	this.handlerObj = null;
	this.output = null;
	this.headers = null;
	for (var i = 0; i < this.cookies.length; i++) {
		this.cookies[i].destroy();
		this.cookies[i] = null;
	}
	this.cookies = null;
	this.contentType = null;
}

